<template>
    <view class="uni-forms" :class="{ 'uni-forms--top': !border }">
        <form @submit.stop="submitForm" @reset="resetForm">
            <slot></slot>
        </form>
    </view>
</template>

<script>
    // #ifndef VUE3
    import Vue from 'vue';

    Vue.prototype.binddata = function (name, value, formName) {
        if (formName) {
            this.$refs[formName].setValue(name, value);
        } else {
            let formVm;
            for (let i in this.$refs) {
                const vm = this.$refs[i];
                if (vm && vm.$options && vm.$options.name === 'uniForms') {
                    formVm = vm;
                    break;
                }
            }
            if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
            formVm.setValue(name, value);
        }
    };
    // #endif


    import Validator from './validate.js';

    /**
     * Forms 表单
     * @description 由输入框、选择器、单选框、多选框等控件组成，用以收集、校验、提交数据
     * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
     * @property {Object} rules    表单校验规则
     * @property {String} validateTrigger = [bind|submit]    校验触发器方式 默认 submit
     * @value bind        发生变化时触发
     * @value submit    提交时触发
     * @property {String} labelPosition = [top|left]    label 位置 默认 left
     * @value top        顶部显示 label
     * @value left    左侧显示 label
     * @property {String} labelWidth    label 宽度，默认 65px
     * @property {String} labelAlign = [left|center|right]    label 居中方式  默认 left
     * @value left        label 左侧显示
     * @value center    label 居中
     * @value right        label 右侧对齐
     * @property {String} errShowType = [undertext|toast|modal]    校验错误信息提示方式
     * @value undertext    错误信息在底部显示
     * @value toast            错误信息toast显示
     * @value modal            错误信息modal显示
     * @event {Function} submit    提交时触发
     */

    export default {
        name: 'uniForms',
        components: {},
        emits: ['input', 'reset', 'validate', 'submit'],
        props: {
            // 即将弃用
            value: {
                type: Object,
                default() {
                    return {};
                }
            },
            // 替换 value 属性
            modelValue: {
                type: Object,
                default() {
                    return {};
                }
            },
            // 表单校验规则
            rules: {
                type: Object,
                default() {
                    return {};
                }
            },
            // 校验触发器方式，默认 关闭
            validateTrigger: {
                type: String,
                default: ''
            },
            // label 位置，可选值 top/left
            labelPosition: {
                type: String,
                default: 'left'
            },
            // label 宽度，单位 px
            labelWidth: {
                type: [String, Number],
                default: ''
            },
            // label 居中方式，可选值 left/center/right
            labelAlign: {
                type: String,
                default: 'left'
            },
            errShowType: {
                type: String,
                default: 'undertext'
            },
            border: {
                type: Boolean,
                default: false
            }
        },
        data() {
            return {
                formData: {}
            };
        },
        computed: {
            dataValue() {
                if (JSON.stringify(this.modelValue) === '{}') {
                    return this.value
                } else {
                    return this.modelValue
                }
            }
        },
        watch: {
            rules(newVal) {
                // 如果规则发生变化，要初始化组件
                this.init(newVal);
            },
            labelPosition() {
                this.childrens.forEach(vm => {
                    vm.init()
                })
            }
        },
        created() {
            // #ifdef VUE3
            let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
            if (!getbinddata) {
                getApp().$vm.$.appContext.config.globalProperties.binddata = function (name, value, formName) {
                    if (formName) {
                        this.$refs[formName].setValue(name, value);
                    } else {
                        let formVm;
                        for (let i in this.$refs) {
                            const vm = this.$refs[i];
                            if (vm && vm.$options && vm.$options.name === 'uniForms') {
                                formVm = vm;
                                break;
                            }
                        }
                        if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
                        formVm.setValue(name, value);
                    }
                }
            }
            // #endif

            // 存放watch 监听数组
            this.unwatchs = [];
            // 存放子组件数组
            this.childrens = [];
            // 存放 easyInput 组件
            this.inputChildrens = [];
            // 存放 dataCheckbox 组件
            this.checkboxChildrens = [];
            // 存放规则
            this.formRules = [];
            this.init(this.rules);
        },
        // mounted() {
        // 	this.init(this.rules)
        // },
        methods: {
            init(formRules) {
                // 判断是否有规则
                if (Object.keys(formRules).length === 0) {
                    this.formData = this.dataValue
                    return
                }
                ;
                this.formRules = formRules;
                this.validator = new Validator(formRules);
                this.registerWatch();
            },
            // 监听 watch
            registerWatch() {
                // 取消监听,避免多次调用 init 重复执行 $watch
                this.unwatchs.forEach(v => v());
                this.childrens.forEach((v) => {
                    v.init()
                })
                // watch 每个属性 ，需要知道具体那个属性发变化
                Object.keys(this.dataValue).forEach(key => {
                    let watch = this.$watch(
                        'dataValue.' + key,
                        value => {
                            if (!value) return
                            // 如果是对象 ，则平铺内容
                            if (value.toString() === '[object Object]') {
                                for (let i in value) {
                                    let name = `${key}[${i}]`;
                                    this.formData[name] = this._getValue(name, value[i]);
                                }
                            } else {
                                this.formData[key] = this._getValue(key, value);
                            }
                        },
                        {
                            deep: true,
                            immediate: true
                        }
                    );
                    this.unwatchs.push(watch);
                });
            },
            /**
             * 公开给用户使用
             * 设置校验规则
             * @param {Object} formRules
             */
            setRules(formRules) {
                this.init(formRules);
            },
            /**
             * 公开给用户使用
             * 设置自定义表单组件 value 值
             *  @param {String} name 字段名称
             *  @param {String} value 字段值
             */
            setValue(name, value, callback) {
                let example = this.childrens.find(child => child.name === name);
                if (!example) return null;
                value = this._getValue(example.name, value);
                this.formData[name] = value;
                example.val = value;
                return example.triggerCheck(value, callback);
            },

            /**
             * 表单重置
             * @param {Object} event
             */
            resetForm(event) {
                this.childrens.forEach(item => {
                    item.errMsg = '';
                    const inputComp = this.inputChildrens.find(child => child.rename === item.name);
                    if (inputComp) {
                        inputComp.errMsg = '';
                        // fix by mehaotian 不触发其他组件的 setValue
                        inputComp.is_reset = true
                        inputComp.$emit('input', inputComp.multiple ? [] : '');
                        inputComp.$emit('update:modelValue', inputComp.multiple ? [] : '');
                    }
                });

                this.childrens.forEach(item => {
                    if (item.name) {
                        this.formData[item.name] = this._getValue(item.name, '');
                    }
                });

                this.$emit('reset', event);
            },

            /**
             * 触发表单校验，通过 @validate 获取
             * @param {Object} validate
             */
            validateCheck(validate) {
                if (validate === null) validate = null;
                this.$emit('validate', validate);
            },
            /**
             * 校验所有或者部分表单
             */
            async validateAll(invalidFields, type, keepitem, callback) {
                let childrens = []
                for (let i in invalidFields) {
                    const item = this.childrens.find(v => v.name === i)
                    if (item) {
                        childrens.push(item)
                    }
                }

                if (!callback && typeof keepitem === 'function') {
                    callback = keepitem;
                }

                let promise;
                if (!callback && typeof callback !== 'function' && Promise) {
                    promise = new Promise((resolve, reject) => {
                        callback = function (valid, invalidFields) {
                            !valid ? resolve(invalidFields) : reject(valid);
                        };
                    });
                }

                let results = [];
                let newFormData = {};
                if (this.validator) {
                    for (let key in childrens) {
                        const child = childrens[key];
                        let name = child.isArray ? child.arrayField : child.name;
                        if (child.isArray) {
                            if (child.name.indexOf('[') !== -1 && child.name.indexOf(']') !== -1) {
                                const fieldData = child.name.split('[');
                                const fieldName = fieldData[0];
                                const fieldValue = fieldData[1].replace(']', '');
                                if (!newFormData[fieldName]) {
                                    newFormData[fieldName] = {};
                                }
                                newFormData[fieldName][fieldValue] = this._getValue(name, invalidFields[name]);
                            }
                        } else {
                            newFormData[name] = this._getValue(name, invalidFields[name]);
                        }
                        const result = await child.triggerCheck(invalidFields[name], true);
                        if (result) {
                            results.push(result);
                            if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
                        }
                    }
                } else {
                    newFormData = invalidFields
                }
                if (Array.isArray(results)) {
                    if (results.length === 0) results = null;
                }

                if (Array.isArray(keepitem)) {
                    keepitem.forEach(v => {
                        newFormData[v] = this.dataValue[v];
                    });
                }

                if (type === 'submit') {
                    this.$emit('submit', {
                        detail: {
                            value: newFormData,
                            errors: results
                        }
                    });
                } else {
                    this.$emit('validate', results);
                }

                callback && typeof callback === 'function' && callback(results, newFormData);

                if (promise && callback) {
                    return promise;
                } else {
                    return null;
                }
            },
            submitForm() {
            },
            /**
             * 外部调用方法
             * 手动提交校验表单
             * 对整个表单进行校验的方法，参数为一个回调函数。
             */
            submit(keepitem, callback, type) {
                for (let i in this.dataValue) {
                    const itemData = this.childrens.find(v => v.name === i);
                    if (itemData) {
                        if (this.formData[i] === undefined) {
                            this.formData[i] = this._getValue(i, this.dataValue[i]);
                        }
                    }
                }
                if (!type) {
                    console.warn('submit 方法即将废弃，请使用validate方法代替！');
                }
                return this.validateAll(this.formData, 'submit', keepitem, callback);
            },

            /**
             * 外部调用方法
             * 校验表单
             * 对整个表单进行校验的方法，参数为一个回调函数。
             */
            validate(keepitem, callback) {
                return this.submit(keepitem, callback, true);
            },

            /**
             * 部分表单校验
             * @param {Object} props
             * @param {Object} cb
             */
            validateField(props, callback) {
                props = [].concat(props);
                let invalidFields = {};
                this.childrens.forEach(item => {
                    if (props.indexOf(item.name) !== -1) {
                        invalidFields = Object.assign({}, invalidFields, {
                            [item.name]: this.formData[item.name]
                        });
                    }
                });
                return this.validateAll(invalidFields, 'submit', [], callback);
            },

            /**
             * 对整个表单进行重置，将所有字段值重置为初始值并移除校验结果
             */
            resetFields() {
                this.resetForm();
            },

            /**
             * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组，如不传则移除整个表单的校验结果
             */
            clearValidate(props) {
                props = [].concat(props);
                this.childrens.forEach(item => {
                    const inputComp = this.inputChildrens.find(child => child.rename === item.name);
                    if (props.length === 0) {
                        item.errMsg = '';
                        if (inputComp) {
                            inputComp.errMsg = '';
                        }
                    } else {
                        if (props.indexOf(item.name) !== -1) {
                            item.errMsg = '';
                            if (inputComp) {
                                inputComp.errMsg = '';
                            }
                        }
                    }
                });
            },
            /**
             * 把 value 转换成指定的类型
             * @param {Object} key
             * @param {Object} value
             */
            _getValue(key, value) {
                const rules = (this.formRules[key] && this.formRules[key].rules) || [];
                const isRuleNum = rules.find(val => val.format && this.type_filter(val.format));
                const isRuleBool = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
                // 输入值为 number
                if (isRuleNum) {
                    value = isNaN(value) ? value : value === '' || value === null ? null : Number(value);
                }
                // 简单判断真假值
                if (isRuleBool) {
                    value = !value ? false : true;
                }
                return value;
            },
            /**
             * 过滤数字类型
             * @param {Object} format
             */
            type_filter(format) {
                return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
            }
        }
    };
</script>

<style lang="scss">
    .uni-forms {
        // overflow: hidden;
        // padding: 10px 15px;
    }

    .uni-forms--top {
        // padding: 10px 15px;
        // padding-top: 22px;
    }
</style>
